#pragma once
#include "Log.hpp"
#include "util.hpp"
#include "config.hpp"
#include <pthread.h>
#include <unordered_map>

namespace my_cloud{
    class BackupInfo{
    public:
        bool _pack_flag;
        time_t _atime;
        time_t _mtime;
        size_t _fsize;
        std::string _real_path;
        std::string _pack_path;
        std::string _url;
    public:
        bool NewBackupInfo(const std::string &real_path){
            FileUtil fu(real_path);
            if(fu.Exists() == false){
                LOG(WARING, "File Not Exists!");
                return false;
            }
            _real_path = real_path;
            _pack_flag = false;
            _atime = fu.FileATime();
            _mtime = fu.FileMTime();
            _fsize = fu.FileSize();
            Config* conf = Config::GetInstance();
            _pack_path = conf->GetPackDir() + fu.FileName() + conf->GetPackFileSuffix();
            _url = conf->GetDownloadPrefix() + fu.FileName();
            return true;
        }
    };

    class DataManager{
    private:
        static DataManager* _instance;
        static std::mutex mtx;
    private:
        std::string _backup_file;
        pthread_rwlock_t _lock;
        std::unordered_map<std::string, BackupInfo> _table;

        DataManager(){
            _backup_file = Config::GetInstance()->GetBackupFile();
            pthread_rwlock_init(&_lock, NULL);
            InitLoad();
        }
    public:
        static DataManager* GetInstance(){
            if(_instance == nullptr){
                mtx.lock();
                if(_instance == nullptr){
                    _instance = new DataManager;
                }
                mtx.unlock();
            }
            return _instance;
        }
        void Insert(const BackupInfo& info){
            pthread_rwlock_wrlock(&_lock);
            _table[info._url] = info;
            pthread_rwlock_unlock(&_lock);
            Storage();
        }
        void Updata(const BackupInfo& info){
            pthread_rwlock_wrlock(&_lock);
            _table[info._url] = info;
            pthread_rwlock_unlock(&_lock);
            Storage();
        }
        bool GetOneByUrl(const std::string url, BackupInfo* info){
            pthread_rwlock_wrlock(&_lock);
            auto it = _table.find(url);
            if(it != _table.end()){
                *info = it->second;
                pthread_rwlock_unlock(&_lock);
                return true;
            }
            pthread_rwlock_unlock(&_lock);
            LOG(WARING, "Can Not Find " + url);
            return false;
        }
        bool GetOneByRealPath(const std::string& realpath, BackupInfo* info){
            pthread_rwlock_wrlock(&_lock);
            auto it = _table.begin();
            for(;it != _table.end(); ++it){
                if(it->second._real_path == realpath){
                    *info = it->second;
                    pthread_rwlock_unlock(&_lock);
                    return true;
                }
            }
            pthread_rwlock_unlock(&_lock);
            LOG(WARING, "Can Not Find " + realpath);
            return false;
        }
        bool GetAll(std::vector<BackupInfo>* arry){
            pthread_rwlock_wrlock(&_lock);
            auto it = _table.begin();
            for(;it != _table.end(); ++it){
                arry->push_back(it->second);
            }
            pthread_rwlock_unlock(&_lock);
            return true;
        }
        void Storage(){
            std::vector<BackupInfo> arry;
            GetAll(&arry);
            Json::Value root;
            for(int i = 0; i < arry.size(); ++i){
                Json::Value item;
                item["pack_flag"] = arry[i]._pack_flag;
                item["fsize"] = (Json::Int64)arry[i]._fsize;
                item["atime"] = (Json::Int64)arry[i]._atime;
                item["mtime"] = (Json::Int64)arry[i]._mtime;
                item["real_path"] = arry[i]._real_path;
                item["pack_path"] = arry[i]._pack_path;
                item["url"] = arry[i]._url;
                root.append(item);
            }
            std::string content;
            JsonUtil::Serialization(root, &content);
            FileUtil fu(_backup_file);
            fu.SetContent(content);
        }
        bool InitLoad(){
            FileUtil fu(_backup_file);
            if(fu.Exists() == false){
                return true;
            }
            std::string content;
            fu.GetContent(content);
            Json::Value root;
            JsonUtil::UnSerialization(&root, content);
            for(int i = 0; i < root.size(); ++i){
                BackupInfo info;
                info._pack_flag = root[i]["pack_flag"].asBool();
                info._fsize = root[i]["fsize"].asInt64();
                info._atime = root[i]["atime"].asInt64();
                info._mtime = root[i]["mtime"].asInt64();
                info._pack_path = root[i]["pack_path"].asString();
                info._real_path = root[i]["real_path"].asString();
                info._url = root[i]["url"].asString();
                Insert(info);
            }
            return true;
        }
    };
    DataManager* DataManager::_instance = nullptr;
    std::mutex DataManager::mtx;
}